using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace Accountability4net
{
    /// <summary>
    /// Base class for transactions.
    /// </summary>
    public abstract class BaseTransaction : List<IAction>, ITransaction
    {
        /// <summary>
        /// Notify listeners that a property has changed
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
                PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Verifies that the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.DEBIT" />
        /// is equal to the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.CREDIT" />.
        /// It is called from <see cref="ITransaction.Commit()" />.
        /// </summary>
        /// <exception cref="UnbalancedTransactionException">
        /// When the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.DEBIT" />
        /// is not equal to the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.CREDIT" />
        /// </exception>
        protected virtual void VerifyBalance()
        {
            Decimal debit = new Decimal(0);
            Decimal credit = new Decimal(0);
            foreach (IAction action in this)
            {
                if (action.Type == ActionType.DEBIT)
                {
                    debit += action.Amount;
                }
                else // ActionType.CREDIT
                {
                    credit += action.Amount;
                }
            }
            if (debit != credit)
            {
                throw new UnbalancedTransactionException(this
                    , String.Format("Debit={0}, Credit={1}", debit, credit));
            }
        }

        /// <summary>
        /// Overrides <see cref="List{T}.Add(T)"/> to throw exception if the transaction has been
        /// commited.
        /// </summary>
        /// <param name="action">The action</param>
        /// <exception cref="Exception">When called after the transaction has been commited</exception>
        public virtual new void Add(IAction action)
        {
            if (IsCommitted)
            {
                throw new Exception("IsCommited");
            }
            base.Add(action);
        }

        #region ITransaction Members

        /// <summary>
        /// Notification for property changes
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Calls <see cref="VerifyBalance()"/>.
        /// Must be overriden in derived class to commits all actions in the transaction.
        /// </summary>
        /// <exception cref="UnbalancedTransactionException">
        /// This exception occurs if the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.DEBIT" />
        /// is not equal to the sum of amount for <see cref="IAction">actions</see> of type <see cref="ActionType.CREDIT" />
        /// when a <see cref="ITransaction">transaction</see> is <see cref="ITransaction.Commit()">committed</see>.
        /// </exception>
        public virtual void Commit()
        {
            VerifyBalance();
        }

        /// <summary>
        /// Indicates when the real-life transaction occured. This time is always in the past
        /// it is not possible to record future actions.
        /// </summary>
        public abstract DateTimeOffset Timestamp { get; set;}

        private string description_;
        /// <summary>
        /// Description of the account
        /// </summary>
        public virtual string Description
        {
            get
            {
                return description_;
            }
            set
            {
                description_ = value;
                OnPropertyChanged("Description");
            }
        }

        /// <summary>
        /// Is <code>true</code> if the transaction has been successfully commited.
        /// </summary>
        public abstract bool IsCommitted { get;}

        /// <summary>
        /// Unique identifier for the transaction
        /// </summary>
        public abstract Guid ID { get; set;}

        #endregion
    }
}
